package project.newCode;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.swing.JFrame;

import project.hulpPakket.CgPanel;
import project.hulpPakket.Color3f;
import project.hulpPakket.Point3f;
import project.hulpPakket.Scene;
import project.hulpPakket.SceneBuilder;
import project.hulpPakket.Vector3f;
import project.newCode.bbhierarchy.BBBalancedNode;
import project.newCode.gui.Guipanel;
import project.newCode.lights.Light;
import project.newCode.scenegraph.SceneTraverserHittable;
import project.newCode.scenegraph.SceneTraverserPreprocessor;
import project.newCode.surfaces.Hittable;
import project.newCode.Acceleration;

public class testRenderer{

	public static void main(String[] args) {
		new testRenderer();
	}

	private JFrame frame;
	private CgPanel panel;
	private Scene scene;
	private Guipanel gui;
	private int antiAliasing = 4;
	private int blocking = 1;
	private int resolution = 500;
	private boolean relighting = false;
	private CgPanel [] images;
	private int nthreads = 6;
	private String filestring = "/Users/roel/Downloads/practicum1_linux/XML/teapotexample.sdl";
	private JFrame[] frames;
	private ArrayList<int[]> r = new ArrayList<int[]>();
	private ArrayList<int[]> g = new ArrayList<int[]>();
	private ArrayList<int[]> b = new ArrayList<int[]>();
	private ArrayList<int[]> a = new ArrayList<int[]>();
	// For saving intersections
	int [] intersections;
	int maxintersection;
	int minintersection = Integer.MAX_VALUE;
	boolean intersectionmode = false;
	private Acceleration acceleration = Acceleration.BALANCEDBB;


	public testRenderer() {
		SceneBuilder sceneBuilder = new SceneBuilder();
		try {
			scene = sceneBuilder.loadScene(filestring );
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		gui = new Guipanel(this);
		gui.setVisible(true);
		panel = new CgPanel();
		frame = new JFrame();
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) {
				System.exit(0);
			}
		});
		frame.setBounds(0,0,resolution,resolution);
		frame.getContentPane().add(panel);
		frame.setVisible(true);
	}




	public void timePerformance() {
		long starttime = System.currentTimeMillis();
		long buildtime = System.currentTimeMillis();
		if(relighting) {
			startImages();
		}
		intersections = new int[resolution*resolution];
		Hittable t = null;
		switch(this.getAcceleration()) {
			case NONE:
				t = new SceneTraverserHittable(scene);
				break;
			case BALANCEDBB:
				SceneTraverserPreprocessor travP = new SceneTraverserPreprocessor(scene.clone());
				ArrayList<Hittable> contents = travP.getContents();
				t = new BBBalancedNode(contents,0);
				break;
			case GRID:
				break;
			case AREABB:
				break;
		}
		for (int i=0; i<1; i++) {
			panel.clear(0,0,0);
			Thread[] threads = new Thread[nthreads];
			for(int j=0; j<nthreads; j++) {
				threads[j] = new Thread(new renderThread(nthreads,j,scene,antiAliasing,blocking,t));
				threads[j].start();
			}
			for(int j=0; j<nthreads; j++) {
				try {
					threads[j].join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(relighting) {
				for(int s=0; s<frames.length; s++) {
					images[s].repaint();
					try {
						images[s].flush();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				getColors();
				this.repaint();
			}
			else {
				images = new CgPanel[1];
				images[0] = panel;
				getColors();
			}
		}
		long endtime = System.currentTimeMillis();
		System.out.println("RenderTime: " + ((float)(endtime-starttime)/1000F) + " seconds, Buildtime: " + ((float)(buildtime-starttime)/1000F) + " seconds");
		//System.exit(0);
	}

	private void getColors() {
		r = new ArrayList<int[]>();
		g = new ArrayList<int[]>();
		b = new ArrayList<int[]>();
		a = new ArrayList<int[]>();
		for(int i=0; i<images.length; i++) {
			int [] rgbpixels = images[i].getRgbpixels();
			int [] aa = new int [rgbpixels.length];
			int [] ra = new int [rgbpixels.length];  
			int [] ga = new int [rgbpixels.length];  
			int [] ba = new int [rgbpixels.length];
			int current = 0;
			for(int j=0; j<rgbpixels.length; j++) {
				current = rgbpixels[j];
				aa[j]=255<<24;
				ra[j]=255 & current>>16;
				ga[j]=255 & current>>8;
			ba[j]=255 & current;
			}
			a.add(aa);
			r.add(ra);
			g.add(ga);
			b.add(ba);
		}

	}


	private void startImages() {
		int N = scene.getLightNames().length;
		clearExtraPanes();
		images = new CgPanel[N];
		frames = new JFrame[N];
		for (int i=0; i<N; i++) {
			frames[i] = new JFrame(); 
			images[i] = new CgPanel();
			frames[i].setBounds(100*(i+1),100*(i+1),getResolution(),getResolution());
			frames[i].getContentPane().add(images[i]);
			frames[i].setVisible(true);
			images[i].componentResized(null);
		}
	}




	private void clearExtraPanes() {
		if(frames!=null) {
			for(int i=0; i<frames.length; i++) {
				frames[i].dispose();
			}
		}
	}

	class renderThread implements Runnable {

		int nthreads;
		int number;
		Scene scene;
		private int antiAliasing;
		private int blocking;
		Hittable t;
		public renderThread(int nthreads1, int number1, Scene scene1, int antiAliasing,int blocking, Hittable t) {
			nthreads = nthreads1;
			number = number1;
			scene = scene1;
			this.t=t;
			this.antiAliasing=antiAliasing;
			this.blocking = blocking;
		}
		@Override
		public void run() {
			Camera c = scene.getCamera().clone();
			Color3f pc = new Color3f();
			Ray r = c.generateRay(1, 1, panel.getWidth(), panel.getHeight());
			Shader shader = new Shader(scene.getLights(),0.1F);
			Shader [] shaders = new Shader[0];
			if(relighting) {
				shaders = new Shader[frames.length];
				for(int i=0; i<frames.length; i++) {
					shaders[i]=new Shader(scene.getLights().get(i),0F);
					shaders[i].setRelighter(true);
				}
			}
			else {
				shaders = new Shader[1];
				shaders[0] = shader;
				images = new CgPanel[1];
				images[0] = panel;
			}
//			SceneTraverserPreprocessor travP = new SceneTraverserPreprocessor(scene.clone());
//			ArrayList<Hittable> contents = travP.getContents();
//			BBBalancedNode t = new BBBalancedNode(contents,0);
			for (int i=number; i<panel.getWidth(); i=i+nthreads) {
				System.out.println("pixel column "+i+"/"+panel.getWidth());
				for (int j =1; j<panel.getHeight(); j++)
				{
					pc = new Color3f(0,0,0);
					for(int s=0; s<shaders.length; s++) {
						shader = shaders[s];
						pc = new Color3f(0,0,0);
						for (int k=0; k<antiAliasing; k++) {
							r = c.generateRandomRay(i, j, images[s].getWidth(), images[s].getHeight());
							Hitrecord rec = new Hitrecord(0);
							pc = Color3f.Plus(pc, shader.raycolor(r, 0, 10000, t,1,1,rec));
							intersections[i+j*panel.getWidth()] = rec.hitCounter;
							maxintersection = (int) Math.max(maxintersection, rec.hitCounter);
							minintersection = (int) Math.min(minintersection, rec.hitCounter);
						}
						pc = Color3f.Multiply(pc, 1F/((float)antiAliasing));
						images[s].drawPixel(i, j, pc.x, pc.y, pc.z);
					}
				}

				for(int s=0; s<shaders.length; s++) {
					images[s].repaint();
					try {
						images[s].flush();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

		}
	}

	public int getAntiAliasing() {
		return antiAliasing;
	}

	public void setAntiAliasing(int antiAliasing) {
		this.antiAliasing = antiAliasing;
		System.out.println("antialiasing set to "+antiAliasing);
	}

	public int getBlocking() {
		return blocking;
	}

	public void setBlocking(int blocking) {
		this.blocking = blocking;
		System.out.println("blocking set to " + blocking);
	}

	public int getResolution() {
		return resolution;
	}

	public void setResolution(int resolution) {
		this.resolution = resolution;
		frame.setBounds(0, 0, resolution, resolution);
	}

	public Scene getScene() {
		return scene;
	}

	public void setScene(Scene scene) {
		this.scene = scene;
	}

	public void printBBHierarchy() {
		SceneTraverserPreprocessor travP = new SceneTraverserPreprocessor(scene.clone());
		ArrayList<Hittable> contents = travP.getContents();
		BBBalancedNode t = new BBBalancedNode(contents,0);
		try
		{
			PrintWriter pr = new PrintWriter("BBmatlab.txt");    
			t.print(pr);
			pr.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.out.println("No such file exists.");
		}
	}

	public void repaint() {
		clearExtraPanes();
		ArrayList<Light> lights = scene.getLights();
		float rf;
		float gf;
		float bf;
		for (int x = 0; x<panel.getWidth(); x++) {
			for (int y=0; y<panel.getHeight(); y++) {
				rf=0;
				gf=0;
				bf=0;
				if(!intersectionmode) {
					for(int i=0; i<r.size(); i++) {
						if(r.size()>1) {
							rf = rf+lights.get(i).getIntensity()*lights.get(i).getColor().x*r.get(i)[x+y*panel.getWidth()]/255F;
							gf = gf+lights.get(i).getIntensity()*lights.get(i).getColor().y*g.get(i)[x+y*panel.getWidth()]/255F;
							bf = bf+lights.get(i).getIntensity()*lights.get(i).getColor().z*b.get(i)[x+y*panel.getWidth()]/255F;
						}
						else {
							rf = rf+r.get(i)[x+y*panel.getWidth()]/255F;
							gf = gf+g.get(i)[x+y*panel.getWidth()]/255F;
							bf = bf+b.get(i)[x+y*panel.getWidth()]/255F;	
						}
					}
					panel.drawPixel(x, y,(float) Math.min(rf,1),(float) Math.min(gf,1), (float) Math.min(bf,1));
				}
				else {
					float color =(float) (intersections[x+y*panel.getWidth()]-minintersection)/((float) (maxintersection-minintersection));
					panel.drawPixel(x, y,(float) Math.pow(color,0.5),1-(float) Math.pow(color,0.5),1-(float) Math.pow(color, 0.5));

				}
			}
		}
		System.out.println(maxintersection + " "+ minintersection);

		panel.repaint();
		try {
			panel.flush();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}


	}


	public boolean isRelighting() {
		return relighting;
	}

	public void setRelighting(boolean relighting) {
		this.relighting = relighting;
	}

	public int getNthreads() {
		return nthreads;
	}

	public void setNthreads(int nthreads) {
		this.nthreads = nthreads;
	}




	public String getFilestring() {
		return filestring;
	}




	public void setFilestring(String filestring) {
		SceneBuilder sceneBuilder = new SceneBuilder();
		try {
			scene = sceneBuilder.loadScene(filestring );
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}




	public void saveImage(String filename) {
		panel.saveImage(filename);
	}

	public void createMovie(float seconds) {
		int frames = Math.round(seconds*25);
		String filename = "/Users/roel/Desktop/Movie/image";
		for(int i=1; i<=frames; i++) {
			Camera c = scene.getCamera();
			double angle=((2F*i)/((float) frames))*Math.PI;
			Point3f center = new Point3f(-10,-10,5);
			Vector3f direction = new Vector3f((float) (8*Math.cos(angle)),(float) (8*Math.sin(angle)),3);
			c.setEye(Point3f.Plus(center, direction));
			c.setLookat(new Vector3f(center.x,center.y,center.z));
			c.orthoNormalise();
			timePerformance();
			saveImage(filename+String.format("%05d", i)+".png");
		}
	}




	public boolean isIntersectionmode() {
		return intersectionmode;
	}




	public void setIntersectionmode(boolean intersectionmode) {
		this.intersectionmode = intersectionmode;
	}




	public Acceleration getAcceleration() {
		return acceleration;
	}
	public void setAcceleration(Acceleration accelaration) {
		this.acceleration = accelaration;
		System.out.println("Acceleration set to "+accelaration.toString());
	}






}
